﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

#pragma warning disable 0659

namespace ParserCore
{

    // Expression, iliti izrazi, predstavljaju bilo koji deo Matlab koda koji pri izvrsavanju vraca neki rezultat.
    // Na primer : '1 + 3' je izraz, dok 'x = f(3, 6)' to nije (u pitanju je iskaz - statement). Izrazi su obicno deo
    // nekog iskaza ( 'x' i 'f(3, 6)' u gornjem iskazu, na primer). 
    public abstract class Expression : IVisited
    {
        protected Expression()
        { }

        public abstract void Accept(IVisitor visitor);

        public sealed override string ToString()
        {
            var visitor = new ToStringVisitor();
            return visitor.GetString(this);
        }

        public sealed override bool Equals(object obj)
        {
            var visitor = new EqualsVisitor();
            return visitor.IsEqual(this, obj);
        }
    }

    // Primer: 
    //   u Matlabu : 12.5
    //   ovde      : new Constant("12.5")
    public class Constant : Expression
    {
        public enum CType { RealNumber, ImaginaryNumber, String }

        public string Const { get; private set; }
        public CType ConstType {get; private set; }

        public Constant(string value, CType type = CType.String)
            : base()
        {
            Const = value;
            ConstType = type;
        }

        public override void Accept(IVisitor visitor)
        {
            visitor.Visit(this);
        }
    }



    // Primer:
    //   u Matlabu : x
    //   ovde      : new Identifier("x")
    public class Identifier : Expression
    {
        public string ID { get; private set; }

        public Identifier(string id)
            : base()
        { ID = id;  }

        public override void Accept(IVisitor visitor)
        {
            visitor.Visit(this);
        }
    }


    // Primer :
    //   u Matlabu : f( expr1, expr2, expr3)
    //   ovde      : new Call(new Identifier("f"), !expr1, !expr2, !expr3)
    //               gde je !expr1 drvo za izraz expr1
    //
    // Call moze biti poziv funkcije ili izraz za vadjenje podmatrice, u zavisnosti od toga
    // kog je tipa objekat sa imenom "f". Tip objekta ce biti odredjen u toku izvrsavanja na osnovu tabele simbola.
    // Na primer, izraz plot(1:3:9, 1:3:9) moze biti poziv funkcije za iscrtavanje ili, u slucaju da postoji matrica plot 
    // (recimo, ranije je izvrsen iskaz - statement : plot = rand(10, 10) ), vadi podmatricu (vezbe radi, koju ;)
    //
    // Takodje, Call se koristi za operatore, na primer:
    //   u Matlabu : a + 4
    //   ovde      : new Call(new Identifier("+"), new Identifier("a"), new Constant("4") )
    public class Call : Expression
    {
        
        public Expression Called { get; private set; } // moguce je containers.Map(arg1, arg2)
        public Expression[] ArgumentList { get; private set; }

        public Call(Expression called, params Expression[] arglist)
            : base()
        {
            Called = called;
            ArgumentList = arglist;
        }

        public override void Accept(IVisitor visitor)
        {
            visitor.Visit(this);
        }
    }

    // Primer:
    //   u Matlabu: A{1,2}
    // ...
    public class SubCell : Expression
    {
        public Expression CellIdentifier { get; private set; }
        public Expression[] ArgumentList { get; private set; }

        public SubCell(Expression cellIdentifier, params Expression[] arglist)
            : base()
        {
            CellIdentifier = cellIdentifier;
            ArgumentList = arglist;
        }

        public override void Accept(IVisitor visitor)
        {
            visitor.Visit(this);
        }
    }
    // Primer:
    //   u Matlabu : [ 1 2+3; 3 f(7) ]
    //   ovde      : Expression[] vrsta1 = { new Constant("1"), new Call(new Identifier("+"), new Constant("2"),new Constant("3")) };
    //               Expression[] vrsta2 = { new Constant("3"), new Call(new Identifier("f"), new Constant("7")) };
    //               Expression[][] concat = { vrsta1, vrsta2 };
    //
    //               new NDimArrayBuilder( concat );
    //
    //  Kako ovo zapravo funkcionise? U Matlabu, izrazima poput [1 2; 3 4] matrice se grade pomocu niza horizontalnih i vertikalnih
    //  konkatenacija, i to, u nasem slucaju, prvo se horizontalnom konkatenacijom brojeva 1 i 2 (setimo se, u Matlabu su to matrice
    //  reda 1x1), napravi matrica [1 2], pa zatim, slicno, matrica [3 4]. Sada se vertikalnom konkatenacijom dobijenih matrica [1 2]
    //  i [3 4], dobija konacna :
    //    1 2
    //    3 4
    //  Naravno, pored brojeva (tj matrica dimenzija 1x1), moguce je koristiti matrice proizvoljnih dimenzija, kao i proizvoljne 
    //  izraze, dok god se njihovim racunanjem dobijaju matrice valjanih dimenzija (ovo se proverava u toku izvrsavanja programa)
    public class NDimArrayBuilder : Expression
    {
        public Expression[][] Exprs { get; private set; }

        public NDimArrayBuilder(Expression[][] exprs)
            : base()
        {
            Exprs = exprs;
        }

        public override void Accept(IVisitor visitor)
        {
            visitor.Visit(this);
        }
    }
    
    // primer: @(x, y) x + y
    // dozvoljeno je i: 
    // >> f = @(x, y) @(z) x + y + z
    // >> g = f(1,1)
    //    g = @(z) 1 + 1 + z
    // >> g(1)
    //   ans = 3
    // >> f(1,1,1)
    //    ?Error: Too many input arguments
    public class AnonymousFunctionBuilder : Expression
    {
        public Identifier[] Arguments { get; private set; }
        public Expression Body { get; private set; }

        public AnonymousFunctionBuilder(Identifier[] arguments, Expression body)
            : base()
        {
            Arguments = arguments;
            Body = body;
        }

        public override void Accept(IVisitor visitor)
        {
            visitor.Visit(this);
        }
    }

    // Cell je struktura nalik na standardnu matricu, sa tom razlikom da elementi cell-a ne moraju biti istog tipa
    // primer:  { 'string', 1 ; [1 2; 3 4], { 5, 'num' } } je cell dimenzije 2x2
    // CellBuilder se ponasa isto kao NDimArrayBuilder
    public class CellBuilder : Expression
    {
        public Expression[][] Exprs { get; private set; }

        public CellBuilder(Expression[][] exprs)
            : base()
        {
            Exprs = exprs;
        }

        public override void Accept(IVisitor visitor)
        {
            visitor.Visit(this);
        }
    }
}
